using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceStack.Redis;
using ServiceStack.Redis.Pipeline;

namespace PageViewBusiness.CacheManager
{
    public class RedisStackClient : IMyRedisClient
    {

        //private const int port = 6379;
        RedisClient _redisClientWriter;
        private RedisClient _redisClientReader;
        private RedisClient _redisClientFull;
        private static readonly object SynRoot = new object();

        //private static readonly string host = ConfigurationManager.AppSettings["REDIS_FEED_IP"] ?? "117.103.196.29";
        private readonly string _host;
        private readonly int _port;

        private readonly int _db;

        private PooledRedisClientManager _poolRedisClient;

        private BasicRedisClientManager _basicRedisClient;

        private readonly string[] _testReadWriteHosts;

        readonly string[] _testReadOnlyHosts;

        /// <summary>
        /// Gets the redis client full.
        /// </summary>
        public RedisClient RedisClientFull
        {
            get
            {
                if (_redisClientFull == null)
                {
                    lock (SynRoot)
                    {
                        _redisClientFull = new RedisClient(_host, _port);
                        if (_db > 0)
                        {
                            _redisClientFull.Db = _db;
                        }
                    }
                }
                return _redisClientFull;
            }
        }

        /// <summary>
        /// Gets the redis client writer.
        /// </summary>
        public IRedisClient RedisClientWriter
        {
            get
            {

                if (_redisClientWriter == null)
                {
                    lock (SynRoot)
                    {
                        _redisClientWriter = new RedisClient(_host, _port);
                        if (_db > 0)
                        {
                            _redisClientWriter.Db = _db;
                        }
                    }
                }
                return _redisClientWriter;
            }

        }

        /// <summary>
        /// Gets the redis client reader.
        /// </summary>
        public IRedisClient RedisClientReader
        {
            get
            {

                if (_redisClientReader == null)
                {
                    lock (SynRoot)
                    {
                        _redisClientReader = new RedisClient(_host, _port);
                        if (_db > 0)
                        {
                            _redisClientReader.Db = _db;
                        }
                    }
                }
                return _redisClientReader;
            }

        }

        public RedisStackClient(int db)
        {
            this._db = db;
        }

        public RedisStackClient(string host, int port, int db)
        {
            this._db = db;
            this._host = host;
            this._port = port;

            this._testReadOnlyHosts = new string[] { host, host, host };

            this._testReadWriteHosts = new string[] { host, host, host };
        }

        public RedisStackClient(string host, int port)
        {
            this._host = host;
            this._port = port;

            this._testReadOnlyHosts = new string[] { host, host, host };

            this._testReadWriteHosts = new string[] { host, host, host };
        }

        public void DisposeAll()
        {
            if (_redisClientWriter != null && _redisClientWriter.HadExceptions)
            {
                _redisClientWriter = null;
                //Logger.Information("[RedisStackClient] Dispose redisClient");
            }

            if (_redisClientReader != null && _redisClientReader.HadExceptions)
            {
                _redisClientReader = null;
                //Logger.Information("[RedisStackClient] Dispose readonlyClient");
            }

            if (_redisClientFull != null && _redisClientFull.HadExceptions)
            {
                _redisClientFull = null;
                //Logger.Information("[RedisStackClient] Dispose RedisClientFull");
            }
        }

        public void Dispose(RedisClient redisClient)
        {
            if (redisClient != null && redisClient.HadExceptions)
            {
                redisClient = null;
            }
        }

        public IRedisTransaction CreateTransaction()
        {
            try
            {
                return RedisClientWriter.CreateTransaction();
            }
            catch (Exception ex)
            {
                DisposeAll();
                return null;
            }
        }

        public bool Push(string key, string value, bool tail)
        {
            try
            {
                if (tail)
                {
                    RedisClientWriter.AddItemToList(key, value);
                }
                else
                {
                    RedisClientWriter.PrependItemToList(key, value);
                }
                return true;
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public bool EnqueueItemOnList(string key, string value)
        {
            try
            {
                RedisClientWriter.EnqueueItemOnList(key, value);
                return true;
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public string DequeueItemFromList(string key)
        {
            try
            {
                return RedisClientWriter.DequeueItemFromList(key);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return null;
            }
        }

        public bool Push(string key, List<string> listValue, bool tail)
        {
            try
            {
                if (tail)
                {
                    RedisClientWriter.AddRangeToList(key, listValue);
                }
                else
                {
                    RedisClientWriter.PrependRangeToList(key, listValue);
                }
                return true;
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public void AddItemToSet(string key, string value)
        {
            try
            {
                RedisClientWriter.AddItemToSet(key, value);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
            }
        }

        public void AddItemToList(string key, string value)
        {
            try
            {
                RedisClientWriter.AddItemToList(key, value);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                DisposeAll();
            }
        }

        public bool AddItemToSortedSet(string key, string value, double score)
        {
            try
            {
                return RedisClientWriter.AddItemToSortedSet(key, value, score);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                DisposeAll();
                return false;
            }
        }

        public bool Set<T>(string key, T t)
        {
            try
            {
                return RedisClientWriter.Set(key, t);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public bool Set<T>(string key, T t, DateTime expireAt)
        {
            try
            {
                return RedisClientWriter.Set(key, t, expireAt);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }
        public bool Set<T>(string key, T t, TimeSpan expireIn)
        {
            try
            {
                return RedisClientWriter.Set(key, t, expireIn);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public bool TrimList(string key, int start, int end)
        {
            try
            {
                RedisClientWriter.TrimList(key, start, end);
                return true;
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                DisposeAll();
                return false;
            }

        }

        public bool SetNX(string key, string value)
        {

            try
            {
                return RedisClientWriter.SetEntryIfNotExists(key, value);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return false;
            }
        }
        //ICacheClient cacheClient;

        public RedisStackClient()
        {
            //cacheClient = new RedisClient(host, port);
        }

        public bool Set(string key, string value)
        {
            try
            {
                return RedisClientWriter.Set(key, value);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public bool Add(string key, string value)
        {
            try
            {
                return RedisClientWriter.Add(key, value);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public bool Add(string key, long value)
        {
            try
            {
                return RedisClientWriter.Add(key, value);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public bool Add(string key, string value, DateTime expireAt)
        {
            try
            {
                return RedisClientWriter.Add(key, value, expireAt);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                DisposeAll();
                return false;
            }
        }

        public bool Add(string key, string value, TimeSpan expireIn)
        {
            try
            {
                return RedisClientWriter.Add(key, value, expireIn);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                DisposeAll();
                return false;
            }
        }

        public bool Add<T>(string key, T value, TimeSpan expireIn)
        {
            try
            {
                return RedisClientWriter.Add(key, value, expireIn);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                DisposeAll();
                return false;
            }
        }

        public bool Replace(string key, string value)
        {
            try
            {
                return RedisClientWriter.Replace(key, value);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }


        public bool SetAdd(string key, string value)
        {
            throw new NotImplementedException();
        }

        public bool Expire(string key, int time)
        {
            throw new NotImplementedException();
        }

        public bool ExpireAt(string key, DateTime dateTime)
        {
            try
            {
                return RedisClientWriter.ExpireEntryAt(key, dateTime);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public bool ExpireIn(string key, TimeSpan timeSpan)
        {
            try
            {
                return RedisClientWriter.ExpireEntryIn(key, timeSpan);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public void SetWithLock(string key, string value)
        {

            //Acquire lock and never release it
            _redisClientWriter.AcquireLock(key);

            var waitFor = TimeSpan.FromSeconds(10);
            var now = DateTime.Now;

            try
            {
                using (var newClient = new RedisClient(_host, _port))
                {
                    //Attempt to acquire a lock with a 2 second timeout
                    using (newClient.AcquireLock(key, waitFor))
                    {
                        //If lock was acquired this would be incremented to '2'
                        //redisClient.IncrementValue("atomic-counter"); 
                        RedisClientWriter.Set(key, value);
                    }
                }
            }
            catch (TimeoutException tex)
            {
                var timeTaken = DateTime.Now - now;
                Console.WriteLine("After '{0}', Received TimeoutException: '{1}'", timeTaken, tex.Message);

                var counter = _redisClientWriter.GetValue(key);
                Console.WriteLine("atomic-counter remains at '{0}'", counter);

                Dispose(_redisClientWriter);
            }


        }

        public bool ContainsKey(string key)
        {
            try
            {
                return RedisClientReader.ContainsKey(key);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return false;
            }
        }

        public string GetString(string key)
        {
            try
            {
                return RedisClientReader.GetValue(key);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return string.Empty;
            }
        }

        public bool Remove(string key)
        {
            try
            {
                return RedisClientWriter.Remove(key);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return false;
            }
        }

        public int RemoveElementFromList(string key, string element)
        {
            try
            {
                return RedisClientWriter.RemoveItemFromList(key, element);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return -1;
            }
        }

        public byte[][] GetKeys(string[] keys)
        {
            try
            {
                var list = keys.ToList();
                var x = RedisClientReader.GetValues(list);

                var xxx = new byte[x.Count][];
                for (int i = 0; i < x.Count; i++)
                {
                    xxx[i] = Encoding.UTF8.GetBytes(x[i]);
                }

                return xxx;
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return null;
            }

        }

        public List<string> GetKeys(List<string> keys)
        {
            try
            {
                return RedisClientReader.GetValues(keys);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                DisposeAll();
                return null;
            }
        }

        public List<T> GetValues<T>(List<string> keys)
        {
            try
            {
                return RedisClientReader.GetValues<T>(keys);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return null;

            }
        }

        public List<string> GetListRangeString(string key, int start, int end)
        {
            try
            {
                return RedisClientReader.GetRangeFromList(key, start, end);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return null;
            }
        }

        public List<string> GetRangeFromSortedList(string key, int startingFrom, int endingAt)
        {
            try
            {
                return RedisClientReader.GetRangeFromSortedList(key, startingFrom, endingAt);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return null;
            }
        }

        public int GetSortedSetCount(string key)
        {
            try
            {
                return RedisClientReader.GetSortedSetCount(key);
            }
            catch (Exception ex)
            {
                ////Logger.Error(e);
                DisposeAll();
                return 0;
            }
        }

        public int GetListCount(string key)
        {
            try
            {
                return RedisClientReader.GetListCount(key);
            }
            catch (Exception ex)
            {
                ////Logger.Error(e);
                DisposeAll();
                return 0;
            }
        }

        public int RemoveRangeFromSortedSet(string key, int minRank, int maxRank)
        {
            try
            {
                return RedisClientWriter.RemoveRangeFromSortedSet(key, minRank, maxRank);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return 0;
            }
        }

        public List<string> GetRangeFromSortedSet(string key, int fromRank, int toRank)
        {
            try
            {
                return RedisClientReader.GetRangeFromSortedSet(key, fromRank, toRank);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                DisposeAll();
                return null;
            }
        }

        public int GetItemIndexInSortedSet(string key, string item)
        {
            try
            {
                return RedisClientReader.GetItemIndexInSortedSet(key, item);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return -1;
            }
        }

        public List<string> GetRangeFromSortedSetDesc(string key, int fromRank, int toRank)
        {
            try
            {
                return RedisClientReader.GetRangeFromSortedSetDesc(key, fromRank, toRank);
            }
            catch (Exception ex)
            {
                ////Logger.Error(e);
                DisposeAll();
                return null;
            }
        }

        public List<string> GetRangeFromSortedSetByHighestScore(string key, double fromScore, double toScore)
        {
            try
            {
                return RedisClientReader.GetRangeFromSortedSetByHighestScore(key, fromScore, toScore);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                DisposeAll();
                return null;
            }
        }

        public List<string> GetRangeFromSortedSetByLowestScore(string key, double fromScore, double toScore)
        {
            try
            {
                return RedisClientReader.GetRangeFromSortedSetByLowestScore(key, fromScore, toScore);
            }
            catch (Exception ex)
            {
                ////Logger.Error(e);
                DisposeAll();
                return null;
            }
        }

        public IDictionary<string, double> GetAllWithScoresFromSortedSet(string key)
        {
            try
            {
                return RedisClientReader.GetAllWithScoresFromSortedSet(key);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                DisposeAll();
                return new Dictionary<string, double>();
            }
        }

        public List<string> GetAllItemsFromList(string key)
        {
            try
            {
                return RedisClientReader.GetAllItemsFromList(key);
            }
            catch (Exception ex)
            {
                //Logger.Error(e);
                DisposeAll();
                return new List<string>();
            }
        }

        public List<string> GetSetMembersString(string key)
        {
            throw new NotImplementedException();
        }

        public IDisposable AcquireLock(string key)
        {
            return RedisClientWriter.AcquireLock(key);
        }

        public IDisposable AcquireLock(string key, TimeSpan timeout)
        {
            return RedisClientWriter.AcquireLock(key, timeout);
        }

        public long IncrementValue(string key)
        {
            try
            {
                return RedisClientWriter.IncrementValue(key);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
                return -1;
            }
        }
        public T Get<T>(string key)
        {
            try
            {
                return RedisClientReader.Get<T>(key);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return default(T);
            }
        }

        public void SetAll<T>(IDictionary<string, T> dicts)
        {
            try
            {
                RedisClientWriter.SetAll(dicts);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientWriter);
            }

        }

        public IRedisPipeline CreatePipeline()
        {
            return RedisClientFull.CreatePipeline();

        }

        public void FlushDb()
        {
            RedisClientWriter.FlushDb();
        }

        public List<string> SearchKeys(string pattern)
        {
            try
            {
                return RedisClientReader.SearchKeys(pattern);
            }
            catch (Exception e)
            {
                //Logger.Error(e);
                Dispose(_redisClientReader);
                return null;
            }
        }
    }
}